package at.dcmspl;

import at.dcmspl.Exceptions.ExportException;
import java.io.Serializable;
import java.util.LinkedList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.annotation.Resource;
import javax.ejb.AfterBegin;
import javax.ejb.AfterCompletion;
import javax.ejb.SessionContext;
import javax.ejb.Stateful;
import javax.naming.NamingException;
import javax.persistence.EntityManager;
import javax.persistence.NoResultException;
import javax.persistence.PersistenceContext;

/**
 * The Service to access the DcmQueue
 * @author Hannes Stornig, Gosch Daniel
 */
@Stateful
public class DcmQueueService implements DcmQueueProducerServiceRemote, DcmQueueConsumerServiceRemote, Serializable {
    @Resource SessionContext sc;
    
    @PersistenceContext
    private EntityManager em; 
    private List<DcmFile> touchedDcmFiles = new LinkedList<DcmFile>();

    /**
    * Creates a DcmQueue
    * @param name Name of the Queue (Type String)
    */
    @Override
    public void createDcmQueue(String name) {
        DcmQueue queue = new DcmQueue(name);
        em.persist(queue);
        Logger.getLogger(DcmQueueService.class.getName()).log(Level.INFO, "### DcmQueue {0} created", name);
    }
    
    /**
    * Creates a DcmQueue
    * @param dcmqueue (Type DcmQueue)
    */
    @Override
    public void createDcmQueue(DcmQueue dcmqueue) {
        em.persist(dcmqueue);
        Logger.getLogger(DcmQueueService.class.getName()).log(Level.INFO, "### DcmQueue {0} created: ", dcmqueue.getName());
    }
    
    /**
    * Updates the delivered DicomQueue
    * @param dcmqueue (Type DcmQueue)
    */
    @Override
    public void updateDcmQueue(DcmQueue dcmqueue) {
        em.merge(dcmqueue);
        Logger.getLogger(DcmQueueService.class.getName()).log(Level.INFO, "### DcmQueue successfully updated: {0}", dcmqueue.getName());
    }

    /**
    * Removes the delivered DcmQueue
    * @param dcmqueue (Type DcmQueue)
    */
    @Override
    public void removeDcmQueue(DcmQueue dcmqueue) {
        em.remove(em.merge(dcmqueue));
    }   
    

    /**
    * Findes a DcmQueue
    * @param id (Type Object)
    */
    @Override
    public DcmQueue findDcmQueue(Object id) {
        return em.find(DcmQueue.class, id);
    }
    
    /**
    * Findes a DcmQueue by the delivered name
    * @param name (Type String)
    * @return DcmQueue
    */
    @Override
    public DcmQueue findDcmQueueByName(String name) {
        try {
            return  em.createNamedQuery(DcmQueue.FIND_BY_NAME, DcmQueue.class)
                    .setParameter("name", name)
                    .setMaxResults(1)
                    .getSingleResult();
        } catch (NoResultException ex) {
            Logger.getLogger(DcmQueueService.class.getName()).log(Level.SEVERE, "No Result found for the Queue {0}",name);
            return null;
        }
    }
        
    /**
    * delivers the First DcmQueue
    * @return DcmQueue
    */    
    @Override
    public DcmQueue getFirst() {
        try {
            return em.createNamedQuery(DcmQueue.FIND_ALL, DcmQueue.class)
                    .setMaxResults(1)
                    .getSingleResult();
        } catch (NoResultException ex) {
            Logger.getLogger(DcmQueueService.class.getName()).log(Level.SEVERE, "No Result found. Exception: {0}",ex);
            return null;
        }
    }    
    
    /**
    * Adds a DcmFile with the delivered DcmQueue Name and the DcmFile Name
    * @param dcmQueueName Is the name of the specific DcmQueue
    * @param dcmFileName Is the name of the dcmFile to add
    */    
    @Override
    public void addDcmFile(String dcmQueueName, String dcmFileName) {   
        DcmQueue dcmQueue = this.findDcmQueueByName(dcmQueueName);
        if(dcmQueue!=null){
            dcmQueue = em.merge(dcmQueue);
            try {
                DcmFile dcmFile = new DcmFile(dcmFileName,dcmQueue);
                dcmQueue.addDcmFile(dcmFile);
            }catch (ExportException ex) {
                sc.setRollbackOnly();
                Logger.getLogger(DcmQueueService.class.getName()).log(Level.SEVERE, null, ex);
            }catch (NamingException ex) {
                sc.setRollbackOnly();
                Logger.getLogger(DcmQueueService.class.getName()).log(Level.SEVERE, null, ex);
            }
        }else{
            Logger.getLogger(DcmQueueService.class.getName()).log(Level.SEVERE, "###DcmQueue {0} not found!", dcmQueueName);
        }
    }
    
    /**
    * Adds a DcmFile with the delivered DcmQueue and the DcmFile Name
    * @param dcmQueue Is the specific DicomQueue in which the DicomFile is added to
    * @param dcmFileName Is the name of the dcmFile to add
    */    
    @Override
    public DcmQueue addDcmFile(DcmQueue dcmQueue, String dcmFileName) {          
        dcmQueue = em.merge(dcmQueue);
        if(dcmQueue!=null){
            try {
                DcmFile dcmFile = new DcmFile(dcmFileName,dcmQueue);
                dcmQueue.addDcmFile(dcmFile);
            } catch (ExportException ex) {
                sc.setRollbackOnly();
                Logger.getLogger(DcmQueueService.class.getName()).log(Level.SEVERE, null, ex); 
            } catch (NamingException ex) {
                sc.setRollbackOnly();
                Logger.getLogger(DcmQueueService.class.getName()).log(Level.SEVERE, null, ex);
            }
        }else{
            Logger.getLogger(DcmQueueService.class.getName()).log(Level.SEVERE, "###DcmQueue {0} not found!", dcmQueue.getName());
        }

        return dcmQueue;
    }
    
    /**
    * Adds a Dicom File with the delivered Queue and the DcmFile
    * @param dcmQueue Is the specific DicomQueue in which the DicomFile is added to
    * @param dcmFile Is the specific dcmFile
    */  
    @Override
    public void addDcmFile(DcmQueue dcmQueue, DcmFile dcmFile) {     
        dcmQueue = em.merge(dcmQueue);
        if(dcmQueue!=null){
                dcmQueue.addDcmFile(dcmFile);
        }else{
            Logger.getLogger(DcmQueueService.class.getName()).log(Level.SEVERE, "###DcmQueue {0} not found!", dcmQueue.getName());
        }
    }
    
    /**
    * Removes a Dicom File in the specific Queue
    * @param dcmQueue Is the specific DcmQueue in which the DcmFile is added to
    * @param dcmFile Is the specific DcmFile
    * @return DcmQueue
    */    
    @Override
    public DcmQueue removeDcmFile(DcmQueue dcmQueue, DcmFile dcmFile) {
        dcmQueue = em.merge(dcmQueue);
        if(dcmQueue!=null){
            this.touchDcmFile(dcmFile);
            dcmQueue.removeDcmFile(dcmFile);
        }else{
            Logger.getLogger(DcmQueueService.class.getName()).log(Level.SEVERE, "###DcmQueue {0} not found!", dcmQueue.getName());
        }   
        return dcmQueue;
    }
    
    /**
    * Retrieves and removes the Head of this DcmQueue, or returns null if this DcmQueue is empty
    * @param dcmQueue Is the specific DicomQueue
    * @return DcmFile
    */   
    @Override
    public DcmFile poolDcmFile(DcmQueue dcmQueue){
        dcmQueue = em.merge(dcmQueue);
        if(dcmQueue!=null){
            try{
                DcmFile dcmFile = dcmQueue.pool();
                this.touchDcmFile(dcmFile);
                return dcmFile;
            }catch(NullPointerException ex){
                sc.setRollbackOnly();
                Logger.getLogger(DcmQueueService.class.getName()).log(Level.SEVERE, "Queue is empty {0}", ex);
            }catch(NamingException nex){
                sc.setRollbackOnly();
                Logger.getLogger(DcmQueueService.class.getName()).log(Level.INFO, "Naming Exception: {0}",nex);
            }
        }else{
            Logger.getLogger(DcmQueueService.class.getName()).log(Level.SEVERE, "###DcmQueue {0} not found!", dcmQueue.getName());
        }
        return null;
    }
    
    /**
    * Retrieves the DicomObjekt Element at the specific Position of the Queue
    * @param dcmQueue The specific DicomQueue
    * @return DcmFile
    */   
    @Override
    public DcmFile peekDcmFile(DcmQueue dcmQueue){
        dcmQueue = em.merge(dcmQueue);
        if(dcmQueue!=null){
            DcmFile dcmFile = dcmQueue.peek();
            return dcmFile;
        }else{
            Logger.getLogger(DcmQueueService.class.getName()).log(Level.SEVERE, "###DcmQueue {0} not found!", dcmQueue.getName());
        }
            return null;
    }
    
    /**
    * Retreaves the Element on the specified Position of the Queue and Removes it
    * 
    * @param dcmQueue The specific DicomQueue
    * @param index The Index of the specific DicomFile
    */
    @Override
    public DcmFile movetoHead(DcmQueue dcmQueue, int index){
        dcmQueue = em.merge(dcmQueue);
        if(dcmQueue!=null){
            try{
                DcmFile dcmFile = dcmQueue.movetoHead(index);
                this.touchDcmFile(dcmFile);
                return dcmFile;
            }catch(NullPointerException ex){
                Logger.getLogger(DcmQueueService.class.getName()).log(Level.SEVERE, "Queue is empty {0}", ex);
            }
        }else{
            Logger.getLogger(DcmQueueService.class.getName()).log(Level.SEVERE, "###DcmQueue {0} not found!", dcmQueue.getName());
        }
        return null;
    }
    
    /**
    * Removes the Head of the Queue, Returnes the DicomFile if the File is removed
    * @param dcmQueue
    * @return DcmFile
    */
    @Override
    public DcmFile remove(DcmQueue dcmQueue){
        dcmQueue = em.merge(dcmQueue);
        if(dcmQueue!=null){
            DcmFile dcmFile;
                dcmFile = dcmQueue.remove();
            if(dcmFile!=null){
                this.touchDcmFile(dcmFile);
                return dcmFile;
            }else{
                Logger.getLogger(DcmQueueService.class.getName()).log(Level.INFO, "DcmQueue {0} is empty", dcmQueue.getName());
            }    
        }else{
            Logger.getLogger(DcmQueueService.class.getName()).log(Level.SEVERE, "###DcmQueue {0} not found!", dcmQueue.getName());
        }   
        return null;
    }
    
    /**
    * Retrieves the DcmFile Object at the specific Position of the DcmQueue
    * @return DcmFile
    * @param dcmQueue The specific DcmQueue
    * @param index The Index of the specific DcmFile
    */
    @Override
    public DcmFile elementAt(DcmQueue dcmQueue,int index){
        dcmQueue = em.merge(dcmQueue);
        if(dcmQueue!=null){
            DcmFile dcmFile = dcmQueue.elementAt(index);
            this.touchDcmFile(dcmFile);
            return dcmFile;
        }else{
            Logger.getLogger(DcmQueueService.class.getName()).log(Level.SEVERE, "###DcmQueue {0} not found!", dcmQueue.getName());
        }   
        return null;   
    }
    
    /**
    * Find a DcmFile Object by the spezific id
    * @return DcmFile
    * @param id
    * 
    */
    @Override
    public DcmFile findDcmFile(DcmFileCPK id){
        try {
            return  em.createNamedQuery(DcmQueue.FIND_DCMFILE, DcmFile.class)
                    .setParameter("id", id)
                    .setMaxResults(1)
                    .getSingleResult();
        } catch (NoResultException ex) {
            Logger.getLogger(DcmQueueService.class.getName()).log(Level.SEVERE, "No Result found for the DcmFile {0}",id);
            return null;
        }
    }
     
    /*
    private void touchDcmQueue(DcmQueue dcmQueue){
        int counter = 0;
        for(DcmQueue x : this.touchedDcmQueues){//For Each loop
            if(x.getName().equals(dcmQueue.getName())){
                counter++;
            }
        }
        if(counter==0){
            touchedDcmQueues.add(dcmQueue);
        }
    }
    * 
    */
    
    private void touchDcmFile(DcmFile dcmFile){
        int counter = 0;
        for(DcmFile x : this.touchedDcmFiles){//For Each loop
            if(x.getName().equals(dcmFile.getName())){
                counter++;
            }
        }
        if(counter==0){
            this.touchedDcmFiles.add(dcmFile);
        }
    }
    
    /**
    * Retrieves the Count of DcmQueues
    * @return long
    */
    @Override
    public long count() {
        return ((Long) em.createQuery("select count(q) from DcmQueue q").getSingleResult()).longValue();
    }

    /**
    * Retrieves all DcmQueues
    * @return List<DcmQueue>
    */
    @Override
    public List<DcmQueue> getAll() {
        return em.createNamedQuery(DcmQueue.FIND_ALL, DcmQueue.class).getResultList();
    }
    
    /**
     * Getter for the ReferenceCounter of the DcmFiles
     * @param name
     * @return 
     */
    private Long getRefCount(String name){
         try {
            return  em.createNamedQuery(DcmQueue.GET_REFERENCE_COUNT, Long.class)
                    .setParameter("name", name)
                    .setMaxResults(1)
                    .getSingleResult();
        } catch (NoResultException ex) {
            Logger.getLogger(DcmQueueService.class.getName()).log(Level.SEVERE, "No Referencecount found for the DcmFile {0}",name);
            return null;
        }
    }

    @AfterBegin
    void AfterBegin(){
        Logger.getLogger(ProcessingNodeBMT.class.getName()).log(Level.INFO, "!! Transaction in DcmQueueService started");
        touchedDcmFiles.clear();
    }
    
    @AfterCompletion
    void afterCompletition(boolean commit) throws NamingException{
        Logger.getLogger(DcmQueueService.class.getName()).log(Level.INFO, "##AfterCompletion in DcmQueueService. commit= {0}",commit);
        if(touchedDcmFiles != null){
            for(DcmFile dcmFile : touchedDcmFiles){//For Each loop
                Logger.getLogger(DcmQueueService.class.getName()).log(Level.INFO, "{0}","Reference Count for DcmFile:"+dcmFile.getName()+ " Count:"+this.getRefCount(dcmFile.getName()));
                if(this.getRefCount(dcmFile.getName())==0){
                    dcmFile.deleteDcmFile();
                }
            }
        }
    }
}
